--╔══════════════════════════════╗════════════════════════════════════════════════════════════════════════════════════╗
--║ MENU & HUD RELATED FUNCTIONS ║ Menu functions and HUD drawing! Saving/Loading is handled in LUA_SETUP, btw.
--╚══════════════════════════════╝════════════════════════════════════════════════════════════════════════════════════╝
local RAIL = GS_RAILS if RAIL.blockload return end

--Autocorrects X offsets since I develop HUD primarily for 1080p
RAIL.hudX = function(v, FRAC)
	local WIDTH,HEIGHT = v.width(),v.height()
	if HEIGHT == 1080 or WIDTH == 1152 return 0 end --Standard 1080p or that random render that conveniently works.
	if FRAC == nil FRAC = 1 end

	if WIDTH == 1024
		return 18*FRAC --440x900	
	elseif WIDTH == 1440
		return 9*FRAC --1440x900
	elseif WIDTH == 1680
		return 23*FRAC --1680x1050
	else
		if WIDTH == 800
			return -6*FRAC --800x600
		elseif WIDTH == 1600 and HEIGHT == 900 
			return -12*FRAC --1600x900	
		elseif WIDTH == 1366
			return -34*FRAC --1366x768
		elseif HEIGHT == 720
			return -20*FRAC --1280x720
		end
		return 29*FRAC --All other resolutions
	end
end

--Prints messages on multiple fronts at once so players can't miss them.
RAIL.MultiPrint = function(p, TEXT, ACT)
	if not (p and p.valid) or type(TEXT)!="string"
		return --
	end
	if ACT!="NoChat" --This puts the text in chat too.
		chatprintf(p, TEXT, true)
	end
	if ACT!="NoConsole" --This prints it into the console.
		CONS_Printf(p, TEXT)
	end
	if p==displayplayer and p==consoleplayer --Only display quakes and this text for this player!
		if ACT!="NoEcho" and (netgame!=true) --Don't strain the netCMD buffer!
			COM_BufInsertText(p, "cecho "+TEXT)	
		end
		if ACT=="ERROR" --A little effect to go with "error" style messages.
			P_StartQuake(25<<16, 5)
			S_StartSoundAtVolume(nil, sfx_adderr, 100, p)
		end
	end
end

--Conditions you MUST have for the menu to be active.
--They're quite strict here since this is meant to work for ALL characters.
RAIL.MenuConditions = function(p, BUTTONS)
	if p.mo and not (p.bot) and p.mo.valid
		local s = p.mo
		if (p.panim <= PA_WALK) or (p.panim==PA_EDGE)
		or (s.state == S_PLAY_EDGE) or (s.state<=S_PLAY_WALK)
			if not (p.powers[pw_carry])
			and not (p.pflags & (PF_SPINNING|PF_SLIDING))
			and not ( (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_STARTJUMP)) and not P_IsObjectOnGround(s) )
				if BUTTONS==true --Only do these checks on boot.
				
				or (abs(p.cmd.sidemove) < 11) and (abs(p.cmd.forwardmove) < 11) 
				and (abs(s.momz) < 3*s.scale) and (abs(s.pmomz) < 3*s.scale)
				and (p.speed < 33*s.scale)
				
					if not (s.eflags & MFE_SPRUNG)
					and s.health and not (P_PlayerInPain(p))
					and not (splitscreen and (p==players[1]))
						return true --
					end
				end
			end
		end
	end return false --
end

--Save your settings. Menu must be open first.
RAIL.SaveSettings = function(p, s, GS, SILENT)
	if s==nil s = p.mo end if s==nil return end --
	if not (s and s.GSRailMenu) or (splitscreen and p == players[1])
		s.GSRailMenu = nil return --
	end
	local MENU = s.GSRailMenu
	if not (marathonmode)
		RAIL.SaveLoad(p, true, false, (SILENT) and true or nil)
	end
	S_StartSound(nil, sfx_gsralj, p)
	s.GSRailMenu = nil return true --
end

--List of menu options.
local M = {}
M[0] = ""
M[1] = "Camera Options"
M[2] = "Gameplay Options"

M[101] = "Custom Camera"
M[102] = "Sideways Shifting"
M[103] = "Cam Distance"
M[104] = "Cam Height"
M[105] = "Cam Turnspeed"
M[106] = "Auto Adjust Speed"
M[107] = "Centering Speed"

M[201] = "Rail Devmode"
M[202] = "Backup Pose"
M[203] = "Dunce Pose"
M[204] = "Sidehop Controls"
M[205] = "Tutorials"

--List of menu descriptions. Uses same numbers as menu options.
local D = {}
D[0] = "Press LEFT or RIGHT to pick an option!"
D[1] = "Change options for your camera while grinding!"
D[2] = "Change options related to GS Grindrails!"

D[101] = "Attempt to use SRB2's vanilla camera instead?"
D[102] = "Intensity of horizontal camera scrolling after turns."
D[103] = "How far your camera zooms out/in when grinding."
D[104] = "What height your camera will aim to be at when grinding."
D[105] = "Manual camera handling speed. May also\x8c disable\x80 it, or\x88 force it to center."
D[106] = "How fast your camera autoturns when grinding, if at all."
D[107] = "Speed camera centers behind you when holding\x88 forward\x80 or\x8c SPIN/C1"

D[201] = "Displays rail-related info, especially when grinding."
D[202] = "The pose characters without unique SPR2_GRND frames will use."
D[203] = "Dunce Pose with\x84 TOSS FLAG.\x85 Uses SPR2_STND if invalid!"
D[204] = "Require holding SPIN to sidehop or not?"
D[205] = "Show aids such as arrow visuals when you may sidehop?"

--Determine the amount of available options!
local function MAXOPTIONS(SUBMENU) 
	local START,TOTAL = 0,0
	if SUBMENU START = 1+(SUBMENU*100) end
	
	for i = START,START+98
		if M[i]==nil
			return TOTAL or 1 --
		end
		TOTAL = $+1
	end return 1 --
end

--Adjust the colors the menu uses!
local function SHIFTMENUCOLORS(M, SUBMENU)
	if (type(M)!="table") return end

	if SUBMENU == 1 --Camera Menu colorscheme
	
		M.SONICCOLOR = SKINCOLOR_SUPERGOLD2		--Silhouette Color 						-- GSRAIL00
		M.BGCOLOR = SKINCOLOR_RUST				--Background image color 				-- GSRAIL01
		M.JUMPCOLOR = SKINCOLOR_RED				--JUMP text & button color 				-- GSRAIL03 + GSRAIL02
		M.SPINCOLOR = SKINCOLOR_EMERALD			--SPIN text & button color 				-- GSRAIL04 + GSRAIL02
		M.STARCOLOR = SKINCOLOR_HEADLIGHT		--The selection ICON's color. 			-- GSRAIL05
		M.ARROWCOLOR = SKINCOLOR_CLOUDY 		--Sideways arrow color 					-- GSRAIL06	
		M.TEXTLINECOLOR = SKINCOLOR_DREAM		--The line underneath highlighted text  -- GSRAIL07
		M.TEXTBOXCOLOR = SKINCOLOR_GALAXY		--The description box at the bottom. 	-- GSRAIL08
		M.BGTEXTCOLOR = SKINCOLOR_VAPOR 		--GS Grind Rails text 					-- GSRAIL09
		
		M.CAMCOLOR = SKINCOLOR_BUBBLEGUM 		--Camera Icon Color 					-- GSRAIL0A
		M.CONTROLLERCOLOR = SKINCOLOR_GREY 		--Controller Icon Color 				-- GSRAIL0B	
		return 1 --
		
	elseif SUBMENU == 2 --Gameplay Menu colorscheme
	
		M.SONICCOLOR = SKINCOLOR_SUPERPURPLE2	--Silhouette Color 						-- GSRAIL00
		M.BGCOLOR = SKINCOLOR_GALAXY			--Background image color 				-- GSRAIL01
		M.JUMPCOLOR = SKINCOLOR_RED				--JUMP text & button color 				-- GSRAIL03 + GSRAIL02
		M.SPINCOLOR = SKINCOLOR_FOREST			--SPIN text & button color 				-- GSRAIL04 + GSRAIL02
		M.STARCOLOR = SKINCOLOR_LEMON			--The selection ICON's color. 			-- GSRAIL05
		M.ARROWCOLOR = SKINCOLOR_CLOUDY 		--Sideways arrow color 					-- GSRAIL06	
		M.TEXTLINECOLOR = SKINCOLOR_LEMON		--The line underneath highlighted text  -- GSRAIL07
		M.TEXTBOXCOLOR = SKINCOLOR_SUPERORANGE5 --The description box at the bottom. 	-- GSRAIL08
		M.BGTEXTCOLOR=SKINCOLOR_TOPAZ 			--GS Grind Rails text 					-- GSRAIL09
	
		M.CAMCOLOR = SKINCOLOR_PERIDOT 			--Camera Icon Color 					-- GSRAIL0A
		M.CONTROLLERCOLOR=SKINCOLOR_BONE		--Controller Icon Color 				-- GSRAIL0B	
		return 2 --
		
	else --if not SUBMENU --Initial Menu colorscheme
	
		M.SONICCOLOR = SKINCOLOR_SUPERSILVER1	--Silhouette Color 						-- GSRAIL00
		M.BGCOLOR = SKINCOLOR_COBALT			--Background image color 				-- GSRAIL01
		M.JUMPCOLOR = SKINCOLOR_PEPPER			--JUMP text & button color 				-- GSRAIL03 + GSRAIL02
		M.SPINCOLOR = SKINCOLOR_GREEN			--SPIN text & button color 				-- GSRAIL04 + GSRAIL02
		M.STARCOLOR = SKINCOLOR_GOLDENROD		--The selection ICON's color. 			-- GSRAIL05
		M.ARROWCOLOR = SKINCOLOR_CLOUDY 		--Sideways arrow color 					-- GSRAIL06	
		M.TEXTLINECOLOR = SKINCOLOR_LEMON		--The line underneath highlighted text  -- GSRAIL07
		M.TEXTBOXCOLOR = SKINCOLOR_SKY 			--The description box at the bottom. 	-- GSRAIL08
		M.BGTEXTCOLOR = SKINCOLOR_YELLOW 		--GS Grind Rails text 					-- GSRAIL09
		
		M.CAMCOLOR = SKINCOLOR_CYAN 			--Camera Icon Color 					-- GSRAIL0A
		M.CONTROLLERCOLOR=SKINCOLOR_CLOUDY 		--Controller Icon Color 				-- GSRAIL0B		
	end
	return 0
end

--Display the menu's visual background elements.
local function GSRailBackground(v, p,s, M, rX)
	local TIMER = M.TIMER or 1
	local BOOT = M.BOOT or 0
	local FADE = M.FADE and min(FF_TRANS90, (M.FADE/3)<<16) or 0 --If the menu's currently fading in, add translucency.
	
	--Make the main background fully black to keep consistent clarity in the menu.
	v.drawScaled((-250+rX)<<16, -250<<16, 10<<16, v.cachePatch("DSHADOW"), V_SNAPTOLEFT|V_SNAPTOTOP|(max(FADE, FF_TRANS50)))

	--Draw a psychedelic rising stars background
	local PATCH = v.cachePatch("GSRAIL01")
	local CM = v.getColormap(nil, M.BGCOLOR or SKINCOLOR_COBALT)
	if (PATCH!=nil)
		local TEMPTRANS = (max(FADE, FF_TRANS40))
		for d = 1,2
			for i = 0,1
				v.drawScaled(-1, (-TIMER+(512*i))<<16, 1<<16, PATCH, V_SNAPTOLEFT|V_SNAPTOTOP|TEMPTRANS, CM)
			end
			if d==1 and M.TRANSIT
				local NUM = M.TRANSIT
				CM = v.getColormap(nil, SKINCOLOR_BLACK)
				if (NUM > 5)
					TEMPTRANS = min(9, 5+NUM)<<16
				else
					TEMPTRANS = min(9, max(5, 10-NUM))<<16
				end
			else
				break --
			end
		end
	end
	
	--Draw the grinding Sonic silhouette
	v.drawScaled((-3-(BOOT*9))<<16, (9+(BOOT*12))<<16, (1<<16)+(BOOT*5500), v.cachePatch("GSRAIL00"),
	V_SNAPTOLEFT|V_SNAPTOBOTTOM|max(FF_TRANS30, FADE), v.getColormap(nil, M.SONICCOLOR or SKINCOLOR_SUPERSILVER1))
	
	--Draw the scrolling GS Rails Text.
	if M.TEXTPOS!=nil
		PATCH = v.cachePatch("GSRAIL09")
		CM = v.getColormap(nil, M.BGTEXTCOLOR or SKINCOLOR_YELLOW)
		for i = -6,4
			if i==0 continue end	
			if (i > 0) --Bottom Row
				v.drawScaled( (M.TEXTPOS2-234*i)<<16, (BOOT+185)<<16,
				55000+(BOOT*600), PATCH, V_SNAPTOBOTTOM|max(FF_TRANS50, FADE), CM)	
			else --Top Row
				v.drawScaled( (M.TEXTPOS-234*i)<<16, (BOOT+3)<<16,
				55000+(BOOT*600), PATCH, V_SNAPTOTOP|max(FF_TRANS50, FADE), CM)				
			end
		end
	end

	--Draw the dialogue box.
	v.drawScaled((53+rX-(BOOT*3))<<16, (128+(BOOT*2))<<16, 75000+(BOOT*4000), v.cachePatch("GSRAIL08"), 
	V_SNAPTOBOTTOM|max(FF_TRANS30, FADE), v.getColormap(nil, M.TEXTBOXCOLOR or SKINCOLOR_SKY))		

	--Draw the buttons and their text.
	local PATCH = v.cachePatch("GSRAIL02")
	for i = 0,1
		if i==0
			CM = v.getColormap(nil, M.JUMPCOLOR or SKINCOLOR_PEPPER)
		else
			CM = v.getColormap(nil, M.SPINCOLOR or SKINCOLOR_FOREST)
		end
		v.drawScaled(((80*i)+rX+128)<<16, (176+(BOOT*3))<<16, 45000, PATCH, V_SNAPTOLEFT|V_SNAPTOBOTTOM|FADE, CM)
		v.drawScaled(((80*i)+rX+153)<<16, (181+(BOOT*3))<<16, 45000, v.cachePatch((i==0) and "GSRAIL03" or "GSRAIL04"), V_SNAPTOLEFT|V_SNAPTOBOTTOM|FADE, CM)
	end	
	
	local SUBMENU = M.SUBMENU or 0 --What submenu are we in?
	local SELECT = M.SELECT or 0 --Which options did we select?
	
	if not SUBMENU --Draw Controller and Camera icons as buttons on initial menu.
	
		local XO = (SELECT==1) and -1 or 0
		local YO = (SELECT==1) and -3 or 0 
		
		--Camera Icon
		v.drawScaled((53-(BOOT*5)+XO+rX)<<16, (52+YO-(BOOT*8))<<16, ((SELECT==1) and 81000 or 1<<16)+(BOOT*5000), 
		v.cachePatch("GSRAIL0A"), V_SNAPTOBOTTOM|max(SELECT==2 and FF_TRANS30 or 0, FADE), v.getColormap(nil, M.CAMCOLOR or SKINCOLOR_CYAN))
		
		XO = (SELECT==2) and -2 or 0
		YO = (SELECT==2) and -5 or 0 	
		
		--Dreamcast Controller Icon
		v.drawScaled((213+(BOOT*4)+XO+rX)<<16, (50+YO-(BOOT*8))<<16, ((SELECT==2) and 34000 or 28000)+(BOOT*2400),
		v.cachePatch("GSRAIL0B"), V_SNAPTOBOTTOM|max(SELECT==1 and FF_TRANS30 or 0, FADE), v.getColormap(nil, M.CONTROLLERCOLOR or SKINCOLOR_CLOUDY))	
		
		--Arrow Icons
		PATCH = v.cachePatch("GSRAIL06")
		CM = v.getColormap(nil, M.ARROWCOLOR or SKINCOLOR_CLOUDY)
		for i = -1,1,2
			if SELECT==1 and i==-1 or SELECT==2 and i==1 continue end
			local XO = 80 if ((TIMER%6==0) or (TIMER5==0)) XO = 83 end
			
			v.drawScaled((166+(XO*i)+rX)<<16, (137-(BOOT*3))<<16,
			39500, PATCH, V_SNAPTOBOTTOM|FADE|((i!=1) and V_FLIP or 0), CM)	
		end
		
	else
		if SUBMENU==1 
			--Draw Camera icon at the top of the screen
			v.drawScaled((145+rX)<<16, 8<<16, 39500, 
			v.cachePatch("GSRAIL0A"), V_SNAPTOTOP, v.getColormap(nil, M.CAMCOLOR or SKINCOLOR_CYAN))	
			
		elseif SUBMENU==2
			--Draw Controller icon at the top of the screen
			v.drawScaled((142+rX)<<16, 9<<15, 19000, 
			v.cachePatch("GSRAIL0B"), V_SNAPTOTOP, v.getColormap(nil, M.CONTROLLERCOLOR or SKINCOLOR_CLOUDY))			
		end
	end
end

local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end

--Previews pose to use if available.
local function DisplayPose(v, p, s, GS, RO, MENU)
	if RO==nil or RO.backupgrindstate==nil or RO.duncegrindstate ==nil or s.skin==nil
		return --
	end
	local STANCE = RO.backupgrindstate if MENU.SELECT==3 STANCE = RO.duncegrindstate end
	local X,Y = 271,102
	local TRANS,PATCH = 0,nil
	
	if type(STANCE)=="number" and P_IsValidSprite2(s, STANCE)
		local SPRITE2 = skins[s.skin].sprites
		local FRAME,MAXFRAME = A,A
		local TRYSUPER = (p.powers[pw_super] or (s.eflags & MFE_FORCESUPER)) and true or false 
		local ROT = MENU.ROTATE or 1
		if TRYSUPER==true STANCE = $|SS2 end
		for i = 1,2
			if (STANCE!=SPR2_STND)  --This one glitches, use a stationary pose!
				if SPRITE2[STANCE] 
					local NAME = SPRITE2[STANCE]
					if NAME and NAME.numframes
						MAXFRAME = tonumber(NAME.numframes)
					end
				end
				if STANCE==SPR2_EDGE and ((MAXFRAME or 0)<5) MAXFRAME = A end
			end
			PATCH = v.getSprite2Patch(s.skin, (STANCE&~((i!=1) and SS2 or 0)), TRYSUPER, (MAXFRAME) and abs( (leveltime >> 1)%MAXFRAME) or A, ROT, 0)
			if not PATCH and TRYSUPER==true
				TRYSUPER, STANCE, MAXFRAME = false, $ & ~SS2, A continue --
			end break --
		end
	end
	if not PATCH
		v.drawString(X+32, Y-44, "\x85"+"Incompatible", V_SNAPTORIGHT, "small-right")
		PATCH, TRANS = v.cachePatch("WHATD0"), FF_TRANS20
	else --Render a rail underneath the sprite!
		v.drawString(X+32, Y-44, "\x82 "+(tonumber(STANCE) or 0), V_SNAPTORIGHT, "small-right")
		v.drawScaled((X-31)<<16, (Y-2)<<16, 1<<15, v.cachePatch("GSRLGL"), V_SNAPTORIGHT, v.getColormap(nil, SKINCOLOR_RED))
	end
	if PATCH
		v.drawScaled(X<<16, Y<<16, 1<<15, PATCH, TRANS|V_SNAPTORIGHT, v.getColormap(nil, s.color or SKINCOLOR_COBALT))
	end		
end

--Display the GS Grind Rail Menu!
RAIL.RailMenu = function(v, p,s, GS, MENU)
	local rX = 0 //RAIL.hudX(v)*2
	GSRailBackground(v, p, s, MENU, rX) --Draw effects and stuff first.
	
	local BOOT = MENU.BOOT
	local FADE = MENU.FADE and min(FF_TRANS90, (MENU.FADE/3)<<16) or 0 --If the menu's currently fading in, add translucency.

	local SUBMENU = MENU.SUBMENU or 0
	local SELECT = MENU.SELECT or 0 --Which options did we select?
	local X,Y = 210+rX,88-(BOOT*2)+(MENU.EXIT*5) --Where to place the text.

	--Text for the menu selections.
	local TEXT = {}
	for i = 1+(SUBMENU*100),99+(SUBMENU*100)
		if M[i]==nil break end --Can't find anymore options, we're done!
		TEXT[i] = M[i]
	end
	if TEXT[SELECT] --Highlight the selected text as yellow.
		TEXT[SELECT] = "\x82"+$
	end
	
	--What is each option currently set to? Display that.
	local RO,O = p.GSRO, {}
	if not SUBMENU --Different layout for the main menu.
		X = 68+rX
	elseif RO
		if SUBMENU==1
			O[101] = (RO.cam_vanilla) and "\x85"+"OFF" or "\x83"+" ON"
			
			O[102] = RO.cam_shift or 0
			if O[102]==10 
				O[102] = "\x83"+$
			end
			O[103] = RO.cam_dist or 192
			if O[103]==192 
				O[103] = "\x83"+$
			end	
			O[104] = RO.cam_height or 40
			if O[104]==40
				O[104] = "\x83"+$
			end
			O[105] = RO.cam_turnspeed or 100
			if O[105]==7777
				O[105] = "\x8c"+"Cannot Turn"
			elseif O[105]==7778
				O[105] = "\x88"+"Always Center"
			elseif O[105]==100
				O[105] = "\x83"+$
			end
			O[106] = RO.cam_autoturnspeed if O[106]==nil O[106] = 75 end
			if O[106]==75
				O[106] = "\x83"+$
			end
			O[107] = RO.cam_centerspeed or 150
			if O[107]==150
				O[107] = "\x83"+$
			end
		elseif SUBMENU==2
			if not (RO.devmode)
				O[201] = "\x85"+"OFF"
			else
				O[201] = "\x8b"+((RO.devmode==3) and "bold" or (RO.devmode==2) and "thin" or "\x83 on")
			end
			O[202] = (((RO.backupgrindstate or SPR2_STND)==SPR2_ROLL) and "\x83" or "\x8b")+
			spr2names[tonumber(RO.backupgrindstate) or SPR2_STND]
			O[203] = (((RO.duncegrindstate or SPR2_STND)==SPR2_EDGE) and "\x83" or "\x8b")+
			spr2names[tonumber(RO.duncegrindstate) or SPR2_STND]
			
			O[204] = (RO.SideHold==4) and "\x8c"+"S+TILT\x80 or\x8d WEAPs"
			or (RO.SideHold==3) and "\x8a"+"TILT\x80 or\x8d WEAPs" or (RO.SideHold==2) and "\x8d"+"WEAPON NEXT/PREV"
			or (RO.SideHold==1) and "\x8c"+"SPIN+Tilt" or "\x8a"+"Tilt"
			
			O[205] = (RO.HUDaid==2) and "\x87"+"NoHUD" or
			(RO.HUDaid) and "\x83"+"ON" or "\x85"+"OFF"
		end
	end
	
	--Now do all the rendering nonsense for the options. This displays what option you're hovering over.
	if (#TEXT)
		local RFADE = FADE
		local ZSET = (100/MENU.MAXNUM)
		for i = (SUBMENU*100)+1,(SUBMENU*100)+99
			if TEXT[i]==nil break end--And we're done here.
			
			local OPTION = O[i] or ""
			local LENGTH = string.len(OPTION)+string.len(TEXT[i])
			if i==SELECT
				v.drawString(X+LENGTH-MENU.SHIFT, Y, TEXT[i]+"\x80 "+OPTION, RFADE|V_SNAPTOTOP, "thin-center")
			else
				v.drawString(X+LENGTH, Y, TEXT[i]+"\x80 "+OPTION, min(FF_TRANS90, RFADE+FF_TRANS20)|V_SNAPTOTOP, "thin-center")
			end
			if not SUBMENU
				X = $+160
			else
				Y = $+ZSET
				if SUBMENU==1 and RO and (RO.cam_vanilla) --Center camera?
					RFADE = max(FADE or 0, FF_TRANS70)
				end
			end
		end
	end
	--Show what each option is currently set to!
	
	--DESCRIPTIONS! Formatting these are a bit of a bitch.
	local TEXT1,TEXT2 = D[SELECT+(SUBMENU*100)], nil
	if type(TEXT1) == "string"
		local CHARS1,CHARS2 = string.len(TEXT1), 0
		if (CHARS1 > 25) --Automatically cut our description into 2 lines without using \n if it is long enough.
			local CUT = (string.find(TEXT1, " ", 24)) or 26 --Cut text only at the first space we find
			TEXT2 = string.sub(TEXT1, CUT)
			TEXT1 = string.sub(TEXT1, 0, CUT)
			CHARS2 = string.len(TEXT2)
			CHARS1 = string.len(TEXT1)
		end
		local XOFFSET,YOFFSET = min(24, CHARS1*3/2), (BOOT*3)-MENU.SHIFT
		local CM = (MENU.SHIFT) and "\x82" or "\x80"
		v.drawString(161, Y+YOFFSET+50, CM+TEXT1, V_ALLOWLOWERCASE|FADE|V_SNAPTOBOTTOM, "thin-center")
		if TEXT2
			v.drawString(161, Y+YOFFSET+60, CM+TEXT2, V_ALLOWLOWERCASE|FADE|V_SNAPTOBOTTOM, "thin-center")
		end
	end
	
	if MENU.MAXNUM and SUBMENU and SELECT
		local ZSET = (SUBMENU==1) and 46 or 53
		local ZADD = ((SUBMENU==1) and 98 or 90)/MENU.MAXNUM
		local TR,RFADE = FF_TRANS30, FADE	
		local rX2 = RAIL.hudX(v)
		for i = 1, MENU.MAXNUM --Render out the options.
			if M[i+(SUBMENU*100)]
				local TR = FF_TRANS30		
				local CM2 = "\x80"
				local XO = -4
				if (i==SELECT)
					TR = 0
					XO = 0
					CM2 = (MENU.SHIFT) and "\x88" or "\x82"
					
					local PERMIT = 999
					if SUBMENU==2 and (SELECT<=3)
						PERMIT = 1
					elseif tonumber(O[i+(SUBMENU*100)])!=nil
						PERMIT = 0
					end
					if PERMIT!=999 --Show arrows if the current option can be a number.
						local XO2 = (((MENU.TIMER%11==0) or (MENU.TIMER%12==0)) and 15 or 14) + PERMIT	
						local CM3 = v.getColormap(nil, M.ARROWCOLOR or SKINCOLOR_CLOUDY)
						for d = -1,1,2
							v.drawScaled( (191+rX2+PERMIT+(XO2*d))<<16, ZSET<<16, 11800, 
							v.cachePatch("GSRAIL06"), V_SNAPTORIGHT|V_SNAPTOTOP|RFADE|((d!=1) and V_FLIP or 0), CM3)
						end
					end
					
					 --Draw a star for the selection cursor
					v.drawScaled((107-rX2)<<16, (ZSET-2)<<16, 26000, v.cachePatch("GSRAIL05"), V_SNAPTOLEFT|V_SNAPTOTOP|RFADE, 
					v.getColormap(nil, MENU.FLASH and SKINCOLOR_MINT or M.STARCOLOR or SKINCOLOR_GOLDENROD))		
					
					--Draw an underline as highlight
					v.drawScaled((129-rX2)<<16, (ZSET+9)<<16, 37500, v.cachePatch("GSRAIL07"), V_SNAPTOLEFT|V_SNAPTOTOP|RFADE,  --Draw an underline as highlight
					v.getColormap(nil, MENU.FLASH and SKINCOLOR_PERIDOT or M.TEXTLINECOLOR or SKINCOLOR_LEMON))	
					if SUBMENU==2 and (SELECT==3 or SELECT==2)
						DisplayPose(v, p, s, GS, RO, MENU)
					end
				elseif SUBMENU==1 and RO
					if (i!=1) and (RO.cam_vanilla) --Fade out the rest if the camera is turned off.
					or RO.cam_turnspeed==7778 and (i>5) --If "Center Always" is on, fade out the rest.
						TR = FF_TRANS70
						RFADE = max(FADE, FF_TRANS70)
					end
				end
				--Display the menu option's name.
				v.drawString(136+XO-rX2, ZSET, CM2+M[i+(SUBMENU*100)], max(TR, RFADE)|V_SNAPTOLEFT|V_SNAPTOTOP, "thin")
				
				if (O[i+(SUBMENU*100)])!=nil --Now show what each option is currently set to!
					v.drawString(180-(XO/2)+rX2, ZSET, CM2+": "+O[i+(SUBMENU*100)], max(TR, RFADE)|V_SNAPTORIGHT|V_SNAPTOTOP, "thin")
				end
				ZSET = $+ZADD
			end
		end
	end
	
	--Yes, you may exit the menu.
	v.drawString(7, 191+BOOT, "\x85"+"SPIN:\x80 "+((SUBMENU) and "Previous" or "Save & Exit"),
	V_ALLOWLOWERCASE|V_SNAPTOBOTTOM|V_SNAPTOLEFT|FADE, "small")
end

--Stance sprites that should probably not be used for grinding ever, at least to prevent rollangle crashes.
if RAIL.NOTTHESE==nil RAIL.NOTTHESE = {} end local NOTTHESE = RAIL.NOTTHESE 
for i = SPR2_TRNS,SPR2_NATK --Not the NiGHTS/transform sprites...
	if i and type(i)=="number" NOTTHESE[i] = true end
end
for i = SPR2_SIGN,SPR2_XTRA --Not the Signpost/Fireworks/Monitor/Life/Char Select sprites...
	if i and type(i)=="number" NOTTHESE[i] = true end
end
/*	NOTTHESE[SPR2_FLY_]=true NOTTHESE[SPR2_MLEE]=true NOTTHESE[SPR2_MLEL]=true
	NOTTHESE[SPR2_FIRE]=true NOTTHESE[SPR2_SPIN]=true NOTTHESE[SPR2_BNCE]=true */ --Hmm, do I hate fun enough to forbid these? Nah.
NOTTHESE[SPR2_RIDE]=true NOTTHESE[SPR2_GRND]=true --Yo, these are already used for actual grinding!


--Menu Thinker called from a PreThinkFrame.
RAIL.MenuThinker = function(p, s, MENU)
	if s==nil s = p.mo end if s==nil return end
	local GS = s.GSgrind
	if not (MENU) or (splitscreen and p==players[1]) --First, cancel conditions!
		p.mo.GSRailMenu = nil return false --
	elseif (RAIL.MenuConditions(p, true)!=true)
		RAIL.SaveSettings(p,s, MENU, true) return false --
	elseif (MENU.EXIT > 4)
		RAIL.SaveSettings(p,s, MENU) return false --	
	end
	local RO = p.GSRO	
	local SUBMENU = MENU.SUBMENU or 0
	local MENUSFX = sfx_menu1 or sfx_men1 or sfx_radio --Can you make up your mind, game?
	MENU.MAXNUM = MAXOPTIONS(SUBMENU)
	
	MENU.JUMP = (p.cmd.buttons & BT_JUMP) and $+1 or 0 --Button inputs.
	MENU.SPIN = (p.cmd.buttons & BT_SPIN) and $+1 or 0	
	MENU.CUSTOM1 = (p.cmd.buttons & BT_CUSTOM1) and $+1 or 0
	
	MENU.TIMER = $+1 if (MENU.TIMER > 565) MENU.TIMER = 54 end
	
	if MENU.TRANSIT
		MENU.TRANSIT = $-1
	end

	--Timers.
	if MENU.EXIT --Exit anim
		MENU.EXIT = $+1
		MENU.FADE = max(10, $+6)
		MENU.CSCROLL = 0
		MENU.BOOT = min(16, $+1)
	else
		if MENU.BOOT --Bootup anim
			MENU.BOOT = $-1
		end	
		if MENU.TEXTPOS==nil or (MENU.TEXTPOS < -(234*5))
			MENU.TEXTPOS = -234*3
		else
			MENU.TEXTPOS = $-1 --Upper row of scrolling text! Pixels are 234 wide, btw.
		end
		if MENU.TEXTPOS2==nil or (MENU.TEXTPOS2 > (234*4))
			MENU.TEXTPOS2 = 234*2
		else
			MENU.TEXTPOS2 = $+1 --Lower row of scrolling text!
		end	
		if MENU.SPIN == 1 --Manually exit?
			if SUBMENU
				MENU.SELECT = (MENU.SUBMENU==1) and 1 or 2 
				MENU.SUBMENU = 0 SUBMENU = 0
				MENU.JUMP,MENU.SPIN,MENU.CUSTOM1 = 99<<16,99<<16,99<<16
				SHIFTMENUCOLORS(MENU, SUBMENU) --Return!
				MENU.MAXNUM = MAXOPTIONS(SUBMENU)
				S_StartSound(nil, sfx_gsral9, p)
				MENU.TRANSIT = 5
			else
				MENU.EXIT = 1 
			end
		elseif MENU.CUSTOM1==1
			if SUBMENU==2 --Rotate sprite?
				if MENU.SELECT==2 or MENU.SELECT==3
					MENU.ROTATE = $+1 if (MENU.ROTATE > 8) MENU.ROTATE = 1 end
					S_StartSound(nil, MENUSFX, p)
				end
			end
		end	
		if MENU.FADE --Text fading in
			MENU.FADE = $-1
		end
	end
	
	if SUBMENU --Scrolling behaviours.
	and MENU.DIRECT == "hold" and (abs(p.cmd.sidemove) > 25) --Create some quickscrolling behaviour.
	and not (SUBMENU==1 and MENU.SELECT==1) and not (SUBMENU==2 and MENU.SELECT > 3)
		MENU.CSCROLL = $+1
		if (MENU.CSCROLL > 33)
		or (MENU.CSCROLL > 11) and (MENU.CSCROLL%3==0)
			MENU.DIRECT = nil
		end
	else
		MENU.CSCROLL = 0
	end
	if MENU.FLASH --Frame flash
		MENU.FLASH = max($-1,0)
	end
	if MENU.SHIFT --Scrolling Text Flash + Shift
		MENU.SHIFT = $-1
	end	

	if MENU.EXIT return end --No controlling the menu when exiting it!
	
	local ghs = MENU.banner --Have a banner over your head.
	if not (ghs and ghs.valid)
		MENU.banner = P_SpawnMobjFromMobj(s, 0,0,s.height*3/2, MT_GSRAILGFX)
		ghs = MENU.banner
		ghs.sprite = SPR_GSR2
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.sttfade = true
		ghs.dispoffset = 120
	end
	ghs.color = (MENU.TIMER<5) and SKINCOLOR_SUPERSILVER1 or s.color or SKINCOLOR_COBALT
	ghs.scale = s.scale/3
	ghs.fuse,ghs.tics = 9,9
	ghs.eflags = s.eflags
	local TRANS = max(FF_TRANS10, (10-MENU.TIMER)<<16)
	ghs.frame = 43|TRANS
	P_MoveOrigin(ghs, s.x,s.y,s.z+(s.scale*3)+s.spriteyscale+s.spriteyoffset+(s.height*3/2)-(TRANS*2))
	
	--Directional inputs or activate!
	local FORWARD,SIDE = p.cmd.forwardmove or 0, p.cmd.sidemove or 0 
	
	if not SUBMENU --Controls act bit differently on the initial menu.
		FORWARD = 0
		if MENU.JUMP==1
			SIDE = 0
		end
	else
		if SUBMENU==1 
			if RO
				if RO.cam_vanilla --Can't mess with other options if the custom camera's off.
					MENU.SELECT,FORWARD = 1,0
				elseif RO.cam_turnspeed==7778 and ((MENU.SELECT or 0)>=5) --The others aren't relevant with "Always center"
					MENU.SELECT,FORWARD = min($, 5),max($, 0)
				end
			end
		end
	end
	if MENU.JUMP == 1 or (abs(FORWARD) > 25) or (abs(SIDE) > 25)
		if MENU.DIRECT!="hold" and MENU.JUMP != 1
			MENU.DIRECT = (abs(SIDE) > 25) and "side" or (FORWARD>0) and -1 or 1
		end

		--Changing a setting!
		if (MENU.JUMP == 1 or MENU.DIRECT == "side")
			if MENU.JUMP==1 and (SUBMENU)
				SIDE = 50 FORWARD = 0 MENU.DIRECT = "side" --Jump translates to sideways in most menus.
			end
			local SELECT = MENU.SELECT
			local Y=false
			
			if not SUBMENU --Main Menu.
				if MENU.DIRECT=="side" or (abs(SIDE) > 25)
					if (SIDE > 0)
						MENU.SELECT = (SELECT!=2) and 2 or 1
					else
						MENU.SELECT = (SELECT!=1) and 1 or 2
					end
					SELECT = MENU.SELECT
					S_StartSound(nil, MENUSFX, p)
				elseif MENU.JUMP==1
					if SELECT==1 --To the Camera menu!
						MENU.SUBMENU = 1
					else--if SELECT==2 --To the Gameplay menu!
						MENU.SUBMENU = 2
					end
					S_StartSound(nil, sfx_gsralg, p)
					MENU.TRANSIT = 5
					MENU.SELECT=1
					SHIFTMENUCOLORS(MENU, MENU.SUBMENU)
					MENU.MAXNUM = MAXOPTIONS(MENU.SUBMENU)
				end

			elseif RO
			
				if SUBMENU==1 --Camera Menu
	
					if SELECT == 1 --Toggle the custom camera entirely?
						RO.cam_vanilla = not RO.cam_vanilla
						S_StartSound(nil, (RO.cam_vanilla) and sfx_shldls or sfx_strpst, p)
					elseif SELECT == 2 --Camera height
						local SHIFT = RO.cam_shift or 0
						if (SIDE > 0)
							RO.cam_shift = min(90, SHIFT+1)
						else
							RO.cam_shift = max(-90, SHIFT-1)
						end
						if RO.cam_shift != SHIFT S_StartSound(nil, MENUSFX, p) Y=true end
					elseif SELECT == 3 --Camera distance
						local DIST = RO.cam_dist or 192
						if (SIDE > 0)
							if (DIST>=180) and (DIST < 192)
								RO.cam_dist = 192
							elseif (DIST>=192) and (DIST<200)
								RO.cam_dist = 200
							else
								RO.cam_dist = min(1000, DIST+10)
							end
						else
							if (DIST<=200) and (DIST > 192)
								RO.cam_dist = 192
							elseif (DIST<=192) and (DIST > 180)
								RO.cam_dist = 180
							else
								RO.cam_dist = max(80, DIST-10)
							end
						end
						if RO.cam_dist!=DIST S_StartSound(nil, MENUSFX, p) Y=true end					
					elseif SELECT == 4 --Cam height preference.
						local HEIGHT = RO.cam_height or 0
						
						RO.cam_height = (SIDE > 0) and min(500, HEIGHT+1) or max(1, HEIGHT-1)
						
						if RO.cam_height!=HEIGHT S_StartSound(nil, MENUSFX, p) Y=true end	
						
					elseif SELECT == 5 --Cam turning speed

						local TURN = RO.cam_turnspeed or 100
						if (TURN < 5)
							if TURN==1 and (SIDE < 0)
								RO.cam_turnspeed = 7777 --"Cannot turn"
							elseif (SIDE>0) RO.cam_turnspeed = 5 end
						elseif (TURN >= 7777)
							if TURN==7778 --"Always Center"
								if (SIDE < 0)
									RO.cam_turnspeed = 300
								end
							elseif (SIDE > 0)
								RO.cam_turnspeed = 1
							end
						else
							if (RO.cam_turnspeed or 0)>=300 and (SIDE > 0) 
								RO.cam_turnspeed = 7778 
							else
								RO.cam_turnspeed = (SIDE > 0) and min(300, TURN+5) or max(1, TURN-5)
							end
						end
						if RO.cam_turnspeed!=TURN S_StartSound(nil, MENUSFX, p) Y=true end

					elseif SELECT == 6 --Cam automatic turning speed

						local TURN = RO.cam_autoturnspeed if TURN==nil TURN=100 end
						if (SIDE > 0)
							RO.cam_autoturnspeed = min(300, TURN+5)
						else
							RO.cam_autoturnspeed = max(0, TURN-5)
						end
						if RO.cam_autoturnspeed!=TURN S_StartSound(nil, MENUSFX, p) Y=true end	

					elseif SELECT == 7 --Camera centering speed
						local TURN = RO.cam_centerspeed or 100
						if (TURN < 5)
							if (SIDE>0) RO.cam_centerspeed = 5 end
						else
							RO.cam_centerspeed = (SIDE > 0) and min(300, TURN+5) or max(1, TURN-5)
						end
						if RO.cam_centerspeed!=TURN S_StartSound(nil, MENUSFX, p) Y=true end
					end	
					
				elseif SUBMENU==2 --Gameplay Menu
				
					if SELECT == 1 --Enable Devmode? With which font?
						local DEVMODE = RO.devmode or 0
						if (SIDE > 0)
							RO.devmode = min(3, DEVMODE+1)
						else
							RO.devmode = max(0, DEVMODE-1)
						end
						if RO.devmode != DEVMODE
							S_StartSound(nil, (RO.devmode) and sfx_appear or sfx_notadd, p) Y=true
						end					
					elseif SELECT == 2 or SELECT == 3 --Select backup Grinding Stance. Defaults to rolling.
						if s.skin and skins[s.skin] 
							local MAXSPRITES2 = skins[s.skin].sprites
							if MAXSPRITES2 and (#MAXSPRITES2)
								local MAXNUM,STANCE = #MAXSPRITES2, SPR2_STND
								if SELECT==2
									STANCE = (type(RO.backupgrindstate)!="number") and SPR2_ROLL or RO.backupgrindstate
								else
									STANCE = (type(RO.duncegrindstate)!="number") and SPR2_EDGE or RO.duncegrindstate
								end
								if (type(STANCE)!="number") or (STANCE >= 128) or (STANCE < 0)
									STANCE = SPR2_STND --Failsafe.
								end
								local OGSTANCE = STANCE 
								local ADD = (SIDE<0) and -1 or 1
								
								for i = 1, MAXNUM+1
									STANCE = $+ADD
									if STANCE==nil or (STANCE < SPR2_STND) or (i >= MAXNUM) or (STANCE >= 128)
										STANCE, MENU.CSCROLL = SPR2_STND,0 break --SPR2_STND is always safe to use, I think.
									elseif (P_IsValidSprite2(s, STANCE)) and (NOTTHESE[STANCE]!=true)
										break --
									end
								end
								if SELECT==2
									RO.backupgrindstate = STANCE
									if RO.backupgrindstate!=OGSTANCE
										S_StartSound(nil, MENUSFX, p) Y=true
									end
								else
									RO.duncegrindstate = STANCE
									if RO.duncegrindstate!=OGSTANCE
										S_StartSound(nil, MENUSFX, p) Y=true
									end
								end
							end
						end
					elseif SELECT == 4 --Toggle sidejump controls?
						local OGSIDE = RO.SideHold or 0
						if (SIDE<0)
							if (OGSIDE > 0)
								RO.SideHold = OGSIDE-1
							end
						elseif (OGSIDE < 4)
							RO.SideHold = OGSIDE+1
						end
						if RO.SideHold!=OGSIDE
							S_StartSound(nil, MENUSFX, p) Y=true
						end
					elseif SELECT == 5 --Toggle rail tutorials?
						local OGSIDE = RO.HUDaid or 0
						if (SIDE<0)
							if (OGSIDE > 0)
								RO.HUDaid = OGSIDE-1
							end
						elseif (OGSIDE < 2)
							RO.HUDaid = OGSIDE+1
						end
						if RO.HUDaid!=OGSIDE	
							S_StartSound(nil, (RO.HUDaid==0) and sfx_pop or sfx_ding, p) Y=true
						end
					end
				end
			end
			MENU.DIRECT = "hold"
			if Y==true
				MENU.FLASH = 2 --Little color flash on some elements
			end
			
		elseif type(MENU.DIRECT)=="number" and (SUBMENU) --Scroll up and down?
		
			for i = 1,77 do
				if i==77 MENU.SELECT = 1 break end --Somehow...
				MENU.SELECT = $+MENU.DIRECT
				if (MENU.SELECT > MENU.MAXNUM)
					MENU.SELECT = 1 break --Jump to the beginning.
				elseif (MENU.SELECT < 1) 
					MENU.SELECT = MENU.MAXNUM or 1 break --Jump to the end.
				else
					break --If we're on a valid option we have unlocked, then stop here!
				end
			end
			S_StartSound(nil, MENUSFX, p)
			MENU.SHIFT = 1
			MENU.DIRECT = "hold"
			MENU.CSCROLL = 0
		end
	else
		MENU.DIRECT = nil
	end
	
	--Keep the player's angle like this and lock their controls.
	local NOCONTROL = p.powers[pw_nocontrol] or 0
	if (NOCONTROL >= 0)
		p.powers[pw_nocontrol] = max(3, NOCONTROL)
	end
	if not ((s.flags2 & MF2_TWOD) or twodlevel)
		p.cmd.angleturn = MENU.ANGLE
		s.angle = MENU.ANGLE
	end
	if (MENU.TIMER < 40)
		p.WasInRailMenu = gamemap or 1 --To reset music volume later consistently.
		S_SetInternalMusicVolume( max(20, 60-MENU.TIMER), p)
	end
	p.cmd.buttons,p.cmd.forwardmove,p.cmd.sidemove = 0,0,0
	
	if p.xsonictable	
		local xs = p.xsonictable
		xs.direct = 0
		xs.jump,xs.spin = 999<<16,999<<16
		xs.custom1,xs.custom2,xs.custom3 = 999<<16,999<<16,999<<16
		s.XS_lastNC = 99<<16
	end
	if s.skin == "sms"
		s.SMS_locked = true
		p.spinhold = 99<<16
		p.sms_jumphold = 99<<16	
	elseif p.yusonictable
		local yu = p.yusonictable
		yu.direct = 0
		yu.jump,yu.spin,yu.custom3 = 99<<16,99<<16,99<<16
	end
end



--Boots up AND closes the Grind Rail menu.
RAIL.MenuCommand = function(p, VAR1)
	if not ((gamestate==GS_LEVEL) and p and p.valid and p.mo and not (p.bot) and p.mo.valid) return end --
	local s = p.mo
	local MAKEMENU = nil
	
	if s.GSRailMenu --Player wants to close an open menu, I guess. Sure!
		MAKEMENU = 1
	else
		if (splitscreen and p==players[1]) --Dunno how they even got here.
			RAIL.MultiPrint(p, "\x85"+"P2 can't use this!", "ERROR") return --
		elseif netgame==true and s.GSgrind
			if VAR1=="RL_CHASEOFF"
				s.GSgrind.chasecam = false return --
			elseif VAR1=="RL_CHASEON"
				s.GSgrind.chasecam = true return --
			end
		end
		if RAIL.MenuConditions(p)==true and not (p.powers[pw_nocontrol])
			if not (s.GSgrind) or s.GSgrind.cannotgrind==nil 
				RAIL.SetupMobjRailVars(s)
			end 
			if s.GSgrind and not (s.GSgrind and s.GSgrind.grinding)
				RAIL.MultiPrint(p, "\x84"+"Opened the"+"\x82 GS Rail Menu", "NoEcho")
				MAKEMENU = 2
			end
		end
	end
	if not (MAKEMENU)
		RAIL.MultiPrint(p, "\x85"+"Stand still on a stable spot!", "ERROR")
	else
		local GS,CHECK = s.GSgrind, MAKEMENU
		if not GS or GS.cannotgrind==nil 
			RAIL.SetupMobjRailVars(s) GS = s.GSgrind
		end 
		if s.GSRailMenu or GS==nil or (CHECK!=true) and (RAIL.MenuConditions(p)!=true)
			if s.GSRailMenu
				RAIL.SaveSettings(p,s, GS, nil)
			end
		--	print("\x85 CANCEL MENU CREATION")
			p.pflags = $ & ~PF_FORCESTRAFE
			s.GSRailMenu = nil return false --
		end
		local ADMIN = ((netgame!=true) or (p==server) or IsPlayerAdmin(p)) and true or false
		
		s.GSRailMenu = {
		TIMER = 1, --How long the menu's been active. Counts up by 1 every tic.
		BOOT = 12, --startup animation timer
		EXIT = 0, --Menu exiting timer.
		SELECT = 0, --Currently selected option
		FADE = 31, --Text elements fade-in
		FLASH = 0, --Flash Effects on colored UI.
		SHIFT = 1, --Text Shift/flash
		
		DIRECT = "hold", --Menu inputs. 
		CSCROLL = 0, --For quickscrolling when player holds a direction.
		JUMP = 999<<16,
		SPIN = 999<<16,
		CUSTOM1 = 999<<16,
		ANGLE = p.cmd.angleturn<<16, --Angle to force us into.
		
		LOCKED = {}, --Any options you can't access?
		ADMIN = ADMIN,
		SUBMENU = 0, --Which submenu we're in. There's Camera, Gameplay, and the initial Menu choosing between those two.
		ROTATE = 3, --Direction sprites face.
		}
		local MENU = s.GSRailMenu 
		p.pflags = $|PF_FORCESTRAFE
		p.WasInRailMenu = true
		s.momx,s.momy,p.speed = 0,0,0
		S_StartSound(nil, sfx_gsral5, p)
		
		SHIFTMENUCOLORS(MENU, MENU.SUBMENU) --Add colors to the menu!
		MENU.MAXNUM = MAXOPTIONS(MENU.SUBMENU) --How many options we can access.
		
		RAIL.MenuThinker(p, s, s.GSRailMenu) return true --Did it!
	end
end

--The ONE function I can keep local without worry...!
local function GSRailCommand(p, VAR1)
	RAIL.MenuCommand(p, VAR1)
end
COM_AddCommand("GSRailMenu", GSRailCommand) --Command that lets you pop open the Grind Rail Menu
COM_AddCommand("GSRailsMenu", GSRailCommand)
COM_AddCommand("GSGrindRailsMenu", GSRailCommand)
COM_AddCommand("GSGrindRailMenu", GSRailCommand)
COM_AddCommand("GSGrindRails", GSRailCommand)
COM_AddCommand("GSGrindMenu", GSRailCommand) --Cool. Now nobody can claim the command is too hard to remember.

--╔════════════════════════════╗════════════════════════════════════════════════════════════════════════╗
--║          HUD CODE          ║ HUD for minor control hints, as well as for debugging.
--╚════════════════════════════╝════════════════════════════════════════════════════════════════════════╝

--The HUD drawing function!
RAIL.HUDdraw = function(v,p)
	if (gamestate!=GS_LEVEL) or not (p)
		return --
	end
	local s = p.mo
	if not (s and s.valid) or netgame==true and ((p.jointime or 0) < 5) 
		return --
	end
	local GS = s.GSgrind
	
	if s.GSRailMenu and not (paused) --Render the GSGrindRail menu?
		RAIL.RailMenu(v, p, s, GS, s.GSRailMenu) return --
	end
	if not (GS and p.GSRO) or splitscreen and p==players[1]
		return --
	end
	
	local rail = GS and GS.myrail
	local OPTIONS = p.GSRO
	local SKIN = GS_RAILS_SKINS[s.skin]
	local STATS = SKIN and SKIN["S_SKIN"]
		
	--Rail Devmode HUD. Usable for all players individually.
	if OPTIONS.devmode
		local ZSPACE,FONT = 7, "small" --Properties for font are adjustable!
		if ((OPTIONS.devmode or 0) > 1)
			ZSPACE = 10
			FONT = (OPTIONS.devmode == 2) and "thin" or "left"
		end
		
		--Show non-grinding debug text?
		if not (GS.grinding and GS.myrail and GS.myrail.valid)
			local FAILSTATE = GS.failroll and "\x85"+GS.failroll or 0 
			local SWITCHING = GS.railswitching and "\x82"+GS.railswitching or 0
			local CANNOTGRIND = GS.cannotgrind and "\x85"+GS.cannotgrind or 0
			local TOUCHEDRAIL = GS.justtouchedrailthing and "\x81"+GS.justtouchedrailthing or 0
			local LEFTOVER = "clean"
			if p.powers[pw_carry] == 3888
				LEFTOVER = "\x85"+"[pw_carry]=3888"
			elseif GS.grinding
				LEFTOVER = "\x81"+"GS.grinding:\x80 "+GS.grinding
			elseif GS.myrail
				LEFTOVER = "\x81"+(GS.myrail.valid and GS.myrail.GSrailnum or "???")
			end			
		
			--Everything is setup. Now make a table and flags, and use a for-loop.
			local FLAGS = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE
			if not TOUCHEDRAIL and not CANNOTGRIND and not SWITCHING and not FAILSTATE and LEFTOVER =="clean"
				FLAGS = $+FF_TRANS30
			end
			
			local Xo,Yo = 15,50 --X offset, and Y offset. Yo!
			local CT = "\x8a" --Text Color
			local CAM = "SRB2["+p.awayviewtics+"]"
			if p.awayviewmobj and p.awayviewmobj.valid
				if p.awayviewmobj==GS.railcam
					CAM = "rail[\x82"+p.awayviewtics+"\x80"+"]"
				elseif p.solcam and p.solcam.mobj and p.awayviewmobj==p.solcam.mobj
					CAM = "sol[\x81"+p.awayviewtics+"\x80"+"]"
				else
					CAM = "other[\x88"+p.awayviewtics+"\x80"+"]"
				end
				CAM = $+":\x86 "+p.awayviewmobj.angle/ANG1
			else
				CAM = $+":\x86 "+(p.cmd.angleturn<<16)/ANG1
			end
		
			local TEXT = { --Gonna make a table(array?) here for quick adjustments later.
			[1] = "Failstate: "+"\x80"+FAILSTATE,
			[2] = "Railswitch: "+"\x80"+SWITCHING,
			[3] = "CannotGrind: "+"\x80"+CANNOTGRIND,	
			[4] = "TouchedRail: "+"\x80"+ TOUCHEDRAIL,
			[5] = "Cam: "+"\x80"+CAM
			}
			for i = 1,#TEXT
				v.drawString(Xo, Yo, CT+TEXT[i], FLAGS, FONT)
				Yo = $+ZSPACE
			end				
			return --
		end
		
		--So we ARE grinding! Lots to unravel then.
		
		--Rail connections info
		local NEXTRAIL = rail.GSnextrail and rail.GSnextrail.GSrailnum
		local RAILNUM = rail.GSrailnum
		local PREVRAIL = rail.GSprevrail and rail.GSprevrail.GSrailnum 
		local SPECIAL = "\x82"
		local HINFO = "\x82"+"Zoffset:\x80"+"0"
		if rail.sprite==SPR_NULL or rail.GSinvisiblerail --Keep track of some flags too.
			SPECIAL = $+"INVISIBLE"
		end
		if (RAIL.HangRail(s, rail))
			SPECIAL = (SPECIAL!="\x82") and $+"|HANG" or $+"HANG"
		end
		if type(rail.GStype)=="number"
			SPECIAL = $+((SPECIAL!="\x82") and "\x86"+"|" or "\x86")+"TYPE[\x88"+rail.GStype+"\x86"+"]"+"\x82"
		end	
		if type(rail.GS_windrail)=="number"
			SPECIAL = $+((SPECIAL!="\x82") and "\x8a"+"|" or "\x8a")+"WINDRAIL["+rail.GS_windrail+"]" 
		end
		local LOOP = RAIL.LearnLoopType(rail)
		if LOOP and LOOP.name
			SPECIAL = $+((SPECIAL!="\x82") and "\x8a"+"|" or "\x82")+string.upper(LOOP.name) 
		end		
		if rail.GSforcerailanim
			SPECIAL = $+((SPECIAL!="\x82") and "\x8a"+"|" or "\x8e")+"FORCESPRITE2["+rail.GSforcerailanim+"]" 
		end
		if rail.GSonlyskin
			SPECIAL = $+((SPECIAL!="\x82") and "\x8a"+"|" or "\x8b")+"ONLYSKIN["+rail.GSonlyskin+"]" 
		end
		if rail.GSnotskin
			SPECIAL = $+((SPECIAL!="\x82") and "\x8a"+"|" or "\x8b")+"NOTSKIN["+rail.GSnotskin+"]" 
		end
		if SPECIAL=="\x82" SPECIAL = "\x80 None" end --Aww, nothing special about you, huh?

		if rail.GSinfo --Add HUD info too.
			local INFO = rail.GSinfo
			if type(INFO.hangZoffset)=="number"
				HINFO ="\x82"+"Zoffset:\x80"+INFO.hangZoffset
			end
			if type(INFO.Xoffset)=="number"
				HINFO = $+"\x82"+"| Xoffset:\x80"+INFO.Xoffset
			end
			HINFO = $+"\x80 "
			if type(INFO.spawnleaves)=="number"
				HINFO = $+"| Leaves:"+R_GetNameByColor(INFO.spawnleaves)
			end
			if type(INFO.spawnfire)=="number"
				HINFO = $+"| Leaves:"+R_GetNameByColor(INFO.spawnfire)
			end				
			if type(INFO.spawndust)=="number"
				HINFO = $+"| Dust:"+R_GetNameByColor(INFO.spawndust)
			end		
			if type(INFO.sparkscolor)=="number"
				HINFO = $+"| CustomSparks: "+R_GetNameByColor(INFO.sparkscolor)
			end
		end
		local GRINDFLAGS= "\x80"+"\n"+"GRINDFLAGS:\x88 "
		local JUMPFLAGS = "\x80"+"\n"+"JUMPFLAGS:\x88 "
		local LOOPFLAGS = "\x80"+"\n"+"LOOPFLAGS:\x88 "
		if ((rail.GSgrindflags or 0) >= 65536)
			if (rail.GSgrindflags & 65536)
				GRINDFLAGS = $+"NOABILITY|"
			end
			if (rail.GSgrindflags & 131072)
				GRINDFLAGS = $+"NOCONTROLS|"
			end
			if (rail.GSgrindflags & 262144)
				GRINDFLAGS = $+"AUTOBALANCE|"
			end
			if (rail.GSgrindflags & 524288)
				GRINDFLAGS = $+"ZEROSPEED|"
			end
			if (rail.GSgrindflags & 1048576)
				GRINDFLAGS = $+"HOMING|"
			end
			if (rail.GSgrindflags & 2097152)
				GRINDFLAGS = $+"FIRE|"
			end
			if (rail.GSgrindflags & 4194304)
				GRINDFLAGS = $+"ELEC"
			end	
		else
			if rail.GSgrindflags
				GRINDFLAGS = $+"RESTNUM[\x85"+rail.GSgrindflags+"\x80"+"]"
			else
				GRINDFLAGS = "\x80"
			end
		end
		if ((rail.GSjumpflags or 0) >= 65536)
			if (rail.GSjumpflags & 65536)
				JUMPFLAGS = $+"NOJUMP|"
			end
			if (rail.GSjumpflags & 131072)
				JUMPFLAGS = $+"NOSIDEHOP|"
			end
			if (rail.GSjumpflags & 262144)
				JUMPFLAGS = $+"NOC3STOP|"
			end
			if (rail.GSjumpflags & 524288)
				JUMPFLAGS = $+"GRAVJUMP|"
			end
			if (rail.GSjumpflags & 2097152)
				JUMPFLAGS = $+((rail.GSjumpflags & 1048576) and "TITANJUMP|" or "MEGAJUMP|")
			elseif (rail.GSjumpflags & 1048576)
				JUMPFLAGS = $+"SUPERJUMP|"
			end
		else
			if rail.GSjumpflags
				JUMPFLAGS = $+"RESTNUM[\x85"+rail.GSjumpflags+"\x88"+"]"
			else
				JUMPFLAGS = "\x80"
			end
		end
		if ((rail.GSloopflags or 0)>=FRACUNIT)
			if (rail.GSloopflags & 131072)
				LOOPFLAGS = $+"UPSIDEDOWN|"
			end
			if (rail.GSloopflags & 524288)
				LOOPFLAGS = $+"fLEFTCAM|"
			elseif (rail.GSloopflags & 262144)
				LOOPFLAGS = $+"fRIGHTCAM|"
			end
			if (rail.GSloopflags & 1048576)
				LOOPFLAGS = $+"NOGFX|"
			end
			if (rail.GSloopflags & 2097152)
				LOOPFLAGS = $+"NOGRINDSTART|"
			end
			if (rail.GSloopflags & 4194304)
				LOOPFLAGS = $+"FRONTCAM|"
			end	
			if (rail.GSloopflags & 8388608)
				LOOPFLAGS = $+"\x85"+"NOCAM|"
			end
			if (rail.GSloopflags & 16777216)
				LOOPFLAGS = $+"\x88"+"PAUSE"
			end
		else
			if rail.GSloopflags 
				LOOPFLAGS = $+"RESTNUM[\x85"+rail.GSloopflags+"\x88"+"]"
			else
				LOOPFLAGS = "\x80"
			end
		end
		local SPEEDMODS = ""
		if rail.GSconveyor
			SPEEDMODS = $+"CONVEYOR[\x82"+rail.GSconveyor+"\x88"+"]|"
		end
		if rail.GSrailspeed
			SPEEDMODS = $+"SPEED%[\x82"+rail.GSrailspeed+"\x88"+"] |"
		end
		if rail.GSrailstartcap
			SPEEDMODS = $+"STARTCAP[\x82"+rail.GSrailstartcap+"\x88"+"]"
		end
		if (SPEEDMODS!="")
			SPEEDMODS = "\x80"+"\n"+"SPEEDMODS:\x88 "+SPEEDMODS
		end

		--Progress and distance values.
		local PROGRESS = GS.railprogress and FixedInt(GS.railprogress) or 0
		local XYLENGTH = rail.GSrailXYlength and FixedInt(rail.GSrailXYlength) or 0
		local ZLENGTH = rail.GSrailZdist and FixedInt(rail.GSrailZdist) or 0
		local LENGTH = rail.GSraillength and FixedInt(rail.GSraillength) or 0 
		
		--Angle values
		local RAILANG = RAIL.ANG(rail) RAILANG = $ and FixedInt(AngleFixed(RAILANG)) or 0
		if rail.GSVertRail RAILANG = $+"\x80"+"["+"\x8c"+"V:"+rail.GSVertRail+"\x80"+"]" end
		local SLOPEANG = (rail.GSrailZDelta or 0)/ANG1
		local SANGLE = ""+(s.angle/ANG1)+"\x82 angturn:\x80"+((p.cmd.angleturn<<16)/ANG1)
		local DRAWANGLE = FixedInt(AngleFixed(p.drawangle))
		local DIRECTION = (GS.railspeed < 0) and "\x85"+"backwards" or "\x83"+"forwards"
		if GS.reversing==-1
			DIRECTION = $+"\x80"+"["+"\x82"+"R"+"\x80"+"]"
		end
		
		--Player values
		local SPEED = GS.railspeed and FixedInt(GS.railspeed) or 0
		local TILT = GS.railtilt or 0
		local BALANCE = GS.badbalance and "\x85"+"Bad" or "\x83"+"Good"
		local CROUCHED = (GS.crouchgrind and GS.crouchbutton and not GS.flipanim) and "\x83"+"Yes" or "\x85"+"No"
		if STATS and STATS.CROUCH==false
			CROUCHED = "\x81"+"Incapable"
		elseif GS.cantcrouch
			CROUCHED = "\x89"+"Disabled"
		end
		
		local FALLTIMER = GS.railfalltimer and "\x85"+GS.railfalltimer or 0
		local CAM = ""
		if p.awayviewmobj and p.awayviewmobj.valid
			CAM = ""+(p.awayviewmobj.angle/ANG1)+"\x86"
			if p.awayviewmobj==GS.railcam
				CAM = $+" rail[\x82"+p.awayviewtics+"\x80"+"]"
			elseif p.solcam and p.awayviewmobj==p.solcam.mobj
				CAM = $+" sol[\x81"+p.awayviewtics+"\x80"+"]"
			else
				CAM = $+" other[\x88"+p.awayviewtics+"\x80"+"]"+" type:\x86"+(p.awayviewmobj.type)
			end
		else
			CAM = $+(p.cmd.angleturn<<16)/ANG1+"\x86 SRB2[\x84"+p.awayviewtics+"\x80"+"]"
		end
			
		--Everything is setup. Now make a table and flags, and use a for-loop.
		local FLAGS = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE|FF_TRANS10 
		local Xo,Yo = 15,50 --X offset, and Y offset. Yo!
		local CT = "\x8a" --Text Color
		local TEXT = { --Gonna make a table(array?) here for quick adjustments later.
		[1] = "NextRail: "+"\x80"+NEXTRAIL,
		[2] = "RailNum: "+"\x82"+RAILNUM,
		[3] = "PrevRail: "+"\x80"+PREVRAIL,	
		[4] = "SPECIAL: "+"\x80"+SPECIAL,
		[5] = "EXTRA: "+"\x80"+HINFO+GRINDFLAGS+JUMPFLAGS+LOOPFLAGS+SPEEDMODS,
		
		[6] = "RAILANG: "+"\x80"+RAILANG,
		[7] = "SLOPEANG: "+"\x80"+SLOPEANG,
		[8] = "mo.angle: "+"\x80"+SANGLE,
		[9] = "CAMANG: "+"\x80"+CAM,
		[10] = "p.drawangle: "+"\x80"+DRAWANGLE,
		[11] = "Grind Direction: "+"\x80"+DIRECTION}
		for i = 1,#TEXT
			if i==4 or i==5 CT = "\x8c"
			elseif i==6 CT = "\x89" Yo = (FONT=="small") and $+40 or $+20
			elseif i==8 CT = "\x82" 
			end
			v.drawString(Xo, Yo, CT+TEXT[i], FLAGS, FONT)
			Yo = $+ZSPACE
		end
		
		--Next part is on the right!
		FLAGS = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_ALLOWLOWERCASE|FF_TRANS10
		Xo,Yo = (FONT=="left") and 190 or 230,30
		CT = "\x8e" --Text Color
		TEXT = { --Gonna make the table here for my convenience.
		[1] = "Progress:\x80 "+PROGRESS,
		[2] = "XYlength:\x80 "+XYLENGTH,
		[3] = "Zlength:\x80 "+ZLENGTH,
		[4] = "length:\x80 "+LENGTH,
		
		[5] = "SPEED:\x80 "+SPEED,
		[6] = "TILT:\x80 "+TILT,
		[7] = "FOCUSGRINDING:\x80 "+CROUCHED,
		[8] = "BALANCE:\x80 "+BALANCE,
		[9] = "FALLTIMER:\x80 "+FALLTIMER}
		for i = 1,#TEXT
			if i == 5 CT = "\x84" Yo = (FONT=="small") and $+20 or $+10 end
			v.drawString(Xo, Yo, CT+TEXT[i], FLAGS, FONT)
			Yo = $+ZSPACE
		end
		return --No HUDaid when Devmode is on!
	end
	if (OPTIONS.HUDaid!=1) return end --Only render if HUDaid was asked for.
	
	if OPTIONS.NewbieGrinder and (OPTIONS.NewbieGrinder < 560) --A little fade-in text when the GS table is made!
		local TIME, TRANS = OPTIONS.NewbieGrinder, 0
		if (TIME > 460)
			TRANS = ((TIME-460)/10)<<16
		elseif (TIME < 90)
			TRANS = (10-(TIME/10))<<16
		end
		TRANS = min(FF_TRANS90, max(0, $))
		v.drawString(TIME-220, 192, "\x83"+"Type"+"\x82 GSRailMenu"+"\x83 into the console to access grinding options!", 
		V_SNAPTOBOTTOM|V_ALLOWLOWERCASE|TRANS, "thin")
	end
	if GS.grinding and GS.myrail and s.skin
		local X,Y = 10,131
		local rail = GS.myrail
		local FLAGS = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_ALLOWLOWERCASE|V_PERPLAYER
		local LOOP = RAIL.LearnLoopType(rail)
		local JUMPFLAGS,LOOPFLAGS,GRINDFLAGS,LOOP = rail.GSjumpflags or 0,rail.GSloopflags or 0,rail.GSgrindflags or 0

		if LOOP and LOOP.walljump
			if not (LOOPFLAGS & (262144|524288))
				local CT = "\x81"
				if (p.cmd.buttons & BT_SPIN) CT = "\x83" end
				v.drawString(X, Y, "SPIN(HOLD):"+CT+" Camlock Walljump", FLAGS, "small")
				Y = $+8	
			end
		else
			if STATS and STATS.ACCELSPEED
			or s.skin=="adventuresonic" and p.powers[pw_super]
				if not (rail.GSVertRail)
					local CT = "\x82" if (p.cmd.forwardmove>33) CT = "\x83" end
					v.drawString(X, Y, "Forward:"+CT+" Accelerate", FLAGS, "small")
					Y = $+8
				end
			end				
			if not (s.eflags & MFE_VERTICALFLIP) and not rail.GShangrail
			or (s.eflags & MFE_VERTICALFLIP) and rail.GShangrail
				if not (STATS and STATS.HEROESTWIST==0) and not (rail.GSVertRail) and not (LOOPFLAGS&131072)
					local TNAME = "SPIN"
					if STATS and STATS.TWISTBUTTON --Different button name?
						local TB = STATS.TWISTBUTTON
						TNAME = ""
						if (TB & BT_SPIN) TNAME = $+"SPIN" end
						if (TB & BT_CUSTOM1) TNAME = (TNAME=="") and $+"C1" or $+"/C1" end
						if (TB & BT_CUSTOM2) TNAME = (TNAME=="") and $+"C2" or $+"/C2" end
						if (TB & BT_CUSTOM3) TNAME = (TNAME=="") and $+"C3" or $+"/C3" end
						if (TB & BT_ATTACK) TNAME = (TNAME=="") and $+"FIRE" or $+"/FIRE" end
						if (TB & BT_FIRENORMAL) TNAME = (TNAME=="") and $+"NORMALFIRE" or $+"/NORMALFIRE" end
						if (BT_SHIELD) and (TB & BT_SHIELD) TNAME = (TNAME=="") and $+"SHIELD" or $+"/SHIELD" end
						if TNAME=="" TNAME = "???" end
					end
					local CT = "\x88" if GS.flipanim CT = "\x83" end
					v.drawString(X, Y, TNAME+":"+CT+" Twist Drive", FLAGS, "small")
					Y = $+8
				end
				if not (STATS and STATS.CROUCH==false) and not GS.cantcrouch
					local NAME = "SPIN"
					if STATS and STATS.CROUCHBUTTON --Different button?
						local CB = STATS.CROUCHBUTTON
						NAME = ""
						if (CB & BT_SPIN) NAME = $+"SPIN" end
						if (CB & BT_CUSTOM1) NAME = (NAME=="") and $+"C1" or $+"/C1" end
						if (CB & BT_CUSTOM2) NAME = (NAME=="") and $+"C2" or $+"/C2" end
						if (CB & BT_CUSTOM3) NAME = (NAME=="") and $+"C3" or $+"/C3" end
						if (CB & BT_ATTACK) NAME = (NAME=="") and $+"FIRE" or $+"/FIRE" end
						if (CB & BT_FIRENORMAL) NAME = (NAME=="") and $+"NORMALFIRE" or $+"/NORMALFIRE" end
						if (BT_SHIELD) and (CB & BT_SHIELD) NAME = (NAME=="") and $+"SHIELD" or $+"/SHIELD" end
						if NAME=="" NAME = "???" end
					end
					local CT = "\x84"
					if GS.crouchbutton and not ((GS.flipanim or 0) > 2)
						CT = "\x83"
					end
					v.drawString(X, Y, NAME+"(Hold):"+CT+" Focus Grind", FLAGS, "small")
					Y = $+8
				end
			end
		end
		if not (STATS and STATS.SIDEHOP==0)	
		and not (JUMPFLAGS & 131072) --Sidehop disabled.
		and not (LOOP and (LOOP.sidehop==false or LOOP.jump==0))
		and (GS.TWOD!=true)
			Y = $+5
			local SH = OPTIONS.SideHold
			if SH==2 or SH==3 or SH==4
				v.drawString(X, Y, "Weapon Prev/Next:\x86 Sidehop", FLAGS, "small")
				Y = $+7
			end
			if SH==1 or SH==4
				v.drawString(X, Y, "SPIN&Tilt + JUMP:\x86 SideHop", FLAGS, "small")
			else
				v.drawString(X, Y, "Tilt + JUMP:\x86 SideHop", FLAGS, "small")
			end
		end
	end	
end

--NOTE: hud.add is added in LUA_HOOKS!